/*
 *  Filename:      meos_msg.c
 *  Author:         Erick Huang<erickhuang1989@gmail.com>
 *  Revised:        2014-12-16
 *  Description:   
 *
 *  Copyright (c) Erick Huang. All rights reserved.
 */
#include "meos_api.h"


typedef struct
{
  	LIST   node;
  	uint16 len;
  	uint8  dest_id;
} meos_msg_hdr_t;


#define MEOS_MSG_LEN(msg_ptr)      ((meos_msg_hdr_t *)(msg_ptr) - 1)->len
#define MEOS_MSG_ID(msg_ptr)       ((meos_msg_hdr_t *)(msg_ptr) - 1)->dest_id

#define MEOS_MSG_Q(msg_ptr)       ((LIST *)((meos_msg_hdr_t *)(msg_ptr) - 1))
#define MEOS_MSG_Q_NEXT(msg_ptr)  ((LIST *)((meos_msg_hdr_t *)(msg_ptr) - 1))->next
#define MEOS_MSG_Q_PRE(msg_ptr)   ((LIST *)((meos_msg_hdr_t *)(msg_ptr) - 1))->previous


// Message Pool Definitions
static LIST queue_head;


void meos_msg_init( void )
{
	list_init(&queue_head);
}

uint8 *meos_msg_allocate( uint16 len )
{
 	meos_msg_hdr_t *hdr;

  	if ( len == 0 )
    	return NULL;

  	hdr = (meos_msg_hdr_t *)meos_mem_alloc((uint16)(len + sizeof( meos_msg_hdr_t)));
  	if ( hdr )
  	{
		list_init_node(&hdr->node);
    	hdr->len = len;
    	hdr->dest_id = TASK_NO_TASK;
    	return (uint8 *)(hdr + 1);  //return user msg buf
  	}
  	else
    	return NULL;
}

Status_t meos_msg_deallocate( uint8 *msg_ptr )
{
  	LIST *q;

  	if ( msg_ptr == NULL )
		return INVALID_MSG_POINTER;

  	// don't deallocate queued buffer
  	if ( MEOS_MSG_ID( msg_ptr ) != TASK_NO_TASK )
    	return MSG_BUFFER_NOT_AVAIL;

  	q = MEOS_MSG_Q( msg_ptr );
  	meos_mem_free( (void *)q );
  	return SUCCESS;
}

/*********************************************************************
 * @fn      meos_msg_send
 *
 * @brief
 *
 *    This function is called by a task to either enqueue (front or tail) a command message to the MSG
 *    queue. The destination_task field must refer to a valid task,
 *    since the task ID will be used to send the message to. This 
 *    function will also set a message ready event in the destination
 *    task's event list.
 *
 * @param   uint8 dest_task - Send msg to Task ID
 * @param   uint8 *msg_ptr - pointer to message buffer
 * @param   uint8 urgent - TRUE to front, otherwise tail
 *
 * @return  SUCCESS, INVALID_TASK, INVALID_MSG_POINTER
 */
Status_t meos_msg_send( uint8 dest_task, uint8 *msg_ptr, bool_t urgent )
{
	halIntState_t intState;
	
  	if ( msg_ptr == NULL )
  	{
    	return INVALID_MSG_POINTER;
  	}

  	if ( dest_task >= tasksCnt )
  	{
    	meos_msg_deallocate( msg_ptr );
    	return INVALID_TASK;
  	}

  	// Check the message header
  	if ( MEOS_MSG_Q_PRE( msg_ptr ) != NULL || MEOS_MSG_Q_NEXT( msg_ptr ) != NULL ||
         MEOS_MSG_ID( msg_ptr ) != TASK_NO_TASK )
  	{
    	meos_msg_deallocate( msg_ptr );
    	return INVALID_MSG_POINTER;
  	}

  	MEOS_MSG_ID( msg_ptr ) = dest_task;

	// Hold off interrupts
  	HAL_ENTER_CRITICAL_SECTION(intState);
  	if ( urgent == TRUE )
  	{
		list_insert_front(&queue_head, MEOS_MSG_Q( msg_ptr ));
  	}
  	else
  	{
    	list_insert_tail(&queue_head, MEOS_MSG_Q( msg_ptr ));
  	}
	// Re-enable interrupts
  	HAL_EXIT_CRITICAL_SECTION(intState);

  	// Signal the task that a message is waiting
  	meos_set_event( dest_task, SYS_EVENT_MSG );
  	return SUCCESS;
}

/*********************************************************************
 * @fn      meos_msg_receive
 *
 * @brief
 *
 *    This function is called by a task to retrieve a received command
 *    message. The calling task must deallocate the message buffer after
 *    processing the message using the meos_msg_deallocate() call.
 *
 * @param   uint8 task_id - receiving tasks ID
 *
 * @return  *uint8 - message information or NULL if no message
 */
uint8 *meos_msg_receive( uint8 task_id )
{
	LIST *node;
	meos_msg_hdr_t *found_msg = NULL;
	halIntState_t   intState;

	// Hold off interrupts
  	HAL_ENTER_CRITICAL_SECTION(intState);
	
	node = queue_head.next;
	while ( node != &queue_head)
	{
		if (((meos_msg_hdr_t *)node)->dest_id == task_id)
		{
			if (found_msg == NULL)
			{
				// Save the first one, not stop looking
				found_msg = (meos_msg_hdr_t *)node + 1;
			}
			else
			{
				// Found the second msg, stop looking
        		break;
			}
		}

		node = node->next;
	}

	// Is there more than one?
  	if ( node != &queue_head )
  	{
    	// Yes, Signal the task that a message is waiting
    	meos_set_event( task_id, SYS_EVENT_MSG );
  	}
  	else
  	{
    	// No more
    	meos_clear_event( task_id, SYS_EVENT_MSG );
  	}

  	// Did we find a message?
  	if ( found_msg != NULL )
  	{
    	// Take out of the link list
    	list_delete( MEOS_MSG_Q(found_msg) );
  	}

  	// Release interrupts
  	HAL_EXIT_CRITICAL_SECTION(intState);

  	return (uint8*)found_msg;
}

